#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
#endregion

namespace Homestake_Mine
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        #region Declarations

        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        GameObject terrain;
        GameObject chest;
        GameObject cylinder;
        GameObject ducky;
        GameObject player1;
        GameObject ship;

        Debug debug;
        Announce DisplayCollision;
        Announce DisplayControls;
        Announce DisplayGoals;
        Announce DisplayVictory;
        //HeightMapInfo heightMapInfo;
        //BasicDirectionalLight flashLight;

        // Set distance from the camera of the near and far clipping planes.
        public float nearClip = 1.0f;
        public float farClip = 2000.0f;

        #endregion

        #region Camera Declarations

        // Set the direction the camera points without rotation.
        Vector3 cameraReference = new Vector3(0, 0, 1);
        Vector3 cameraPosition = new Vector3(0,10,0);

        // Set field of view of the camera in radians (pi/4 is 45 degrees).
        static float viewAngle = MathHelper.PiOver4;
        float aspectRatio;

        #endregion

        #region Initialization and Content Loading
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 800;
        }

        protected override void Initialize()
        {
            terrain = new GameObject();
            chest = new GameObject();
            cylinder = new GameObject();
            ducky = new GameObject();
            player1 = new GameObject();
            ship = new GameObject();

            DisplayCollision = new Announce();
            DisplayControls = new Announce();
            DisplayGoals = new Announce();
            DisplayVictory = new Announce();
            debug = new Debug();

            aspectRatio = graphics.GraphicsDevice.Viewport.Height / graphics.GraphicsDevice.Viewport.Width;
            ducky.forwardSpeed = 0.1f;

            player1.Position = new Vector3(0, 1, -20);
            player1.bSphere.Radius = 3;
            player1.bSphere.Center = player1.Position;

            ducky.Position = new Vector3(600, -344, -825);
            ducky.bSphere.Radius = 1;
            ducky.bSphere.Center = ducky.Position;
            ducky.World = Matrix.CreateTranslation(ducky.Position);

            chest.Position = Vector3.Zero;
            chest.bSphere.Radius = 2;
            chest.bSphere.Center = chest.Position;
            chest.World = Matrix.CreateTranslation(chest.Position);

            ship.Position = new Vector3(577, -344, -825);
            ship.bSphere.Radius = 10;
            ship.bSphere.Center = ship.Position;
            ship.World = Matrix.CreateTranslation(ship.Position);

            SetDisplaysForObjects();
            base.Initialize();
        }

        public void SetDisplaysForObjects()
        {
            DisplayControls.SetAnnouncement("Arrow Keys Move 1st Person Camera");
            DisplayControls.Position = new Vector2(GraphicsDeviceManager.DefaultBackBufferWidth - 15, 4);

            DisplayGoals.SetAnnouncement("Explore the Homestake Mine and See what you find!");
            DisplayGoals.Position = new Vector2(GraphicsDeviceManager.DefaultBackBufferWidth - 15, 25);
        }

        /// <summary>             
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            debug.font = Content.Load<SpriteFont>("Arial");

            //Load the Model 
            terrain.model = Content.Load<Model>("Models\\terrain");
            chest.model = Content.Load<Model>("Models\\treasure_chest_2");
            cylinder.model = Content.Load<Model>("Models\\cylinder");
            ducky.model = Content.Load<Model>("Models\\ducky");
            ship.model = Content.Load<Model>("Models\\ship");

            // Textures for .X  files 
            ducky.texture = Content.Load<Texture2D>("Textures\\tDucky");
            chest.texture = Content.Load<Texture2D>("Textures\\tChest");
            ship.texture = Content.Load<Texture2D>("Textures\\tShip");
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }
        #endregion

        #region Inputs and Collision Detection

        private void VictoryCheck()
        {
            if (ship.found && chest.found && ducky.found)
                DisplayCollision.SetAnnouncement("You have found all the objects in the Cave!!");
        }

        private void CollisionCheck()
        {
            if (player1.bSphere.Intersects(ship.bSphere))
                DisplayCollision.SetAnnouncement("You Have Found A Spaceship!!");

            if (player1.bSphere.Intersects(chest.bSphere))
                DisplayCollision.SetAnnouncement("You Have Found The Treasure!!");

            if (player1.bSphere.Intersects(ducky.bSphere))
                DisplayCollision.SetAnnouncement("A duck?");
        }

        private bool IsCollision(GameObject obj1, GameObject obj2)
        {
            for (int meshIndex1 = 0; meshIndex1 < obj1.model.Meshes.Count; meshIndex1++)
            {
                BoundingSphere sphere1 = obj1.model.Meshes[meshIndex1].BoundingSphere;
                sphere1 = sphere1.Transform(obj1.World);

                for (int meshIndex2 = 0; meshIndex2 < obj2.model.Meshes.Count; meshIndex2++)
                {
                    BoundingSphere sphere2 = obj2.model.Meshes[meshIndex2].BoundingSphere;
                    sphere2 = sphere2.Transform(obj2.World);

                    if (sphere1.Intersects(sphere2))
                        return true;
                }
            }
            return false;
        }

        private bool IsCollisionWithTerrain(GameObject obj) { return false; }
        //{
        //    for (int meshIndex = 0; meshIndex < obj.model.Meshes.Count; meshIndex++)
        //    {
        //        BoundingSphere sphere1 = obj.model.Meshes[meshIndex].BoundingSphere;
        //        sphere1 = sphere1.Transform(obj.World);

        //        for (int meshIndex1 = 0; meshIndex1 < terrain.model.Meshes.Count; meshIndex1++)
        //        {
        //            BoundingSphere sphere2 = terrain.model.Meshes[meshIndex1].BoundingSphere;
        //            sphere2 = sphere2.Transform(terrain.World);

        //            if (sphere2.Intersects(sphere1))
        //                return true;
        //        }
        //    }

        //    return false;
        //}

        private void  MoveObjectonWithRotation(GameObject obj)
        {
            // needs to be changed when we add players to it
            KeyboardState state = Keyboard.GetState();

            if (state.IsKeyDown(Keys.W))
            {
                obj.LastPosition = obj.Position;
                Matrix forwardMovement = Matrix.CreateRotationY(obj.objectYaw);
                Vector3 v = new Vector3(0, 0, obj.forwardSpeed);
                v = Vector3.Transform(v, forwardMovement);
                obj.Position.Z += v.Z;
                obj.Position.X += v.X;

                if(IsCollisionWithTerrain(obj))
                    obj.Position = obj.LastPosition;
            }
            else if (state.IsKeyDown(Keys.S))
            {
                obj.LastPosition = obj.Position;
                Matrix forwardMovement = Matrix.CreateRotationY(obj.objectYaw);
                Vector3 v = new Vector3(0, 0, -obj.forwardSpeed);
                v = Vector3.Transform(v, forwardMovement);
                obj.Position.Z += v.Z;
                obj.Position.X += v.X;
                
                if(IsCollisionWithTerrain(obj))
                    obj.Position = obj.LastPosition;
            }

            if (state.IsKeyDown(Keys.D))
            {
                // Rotate right.
                obj.objectYaw -= obj.rotationSpeed;
            }
            else if (state.IsKeyDown(Keys.A))
            {
                // Rotate left.
                obj.objectYaw += obj.rotationSpeed;
            }

            obj.World = Matrix.CreateTranslation(obj.Position);
        }

        private void MoveObjectonByPosition(GameObject obj)
        {
            KeyboardState state = Keyboard.GetState();

            if (state.IsKeyDown(Keys.I))
            {
                obj.LastPosition = obj.Position;
                obj.Position += new Vector3(0, obj.forwardSpeed, 0);
                if (IsCollisionWithTerrain(obj))
                {
                    obj.Position = obj.LastPosition;
                    cameraPosition = obj.LastPosition;
                }
            }
            else if (state.IsKeyDown(Keys.K))
            {
                obj.LastPosition = obj.Position;
                obj.Position -= new Vector3(0, obj.forwardSpeed, 0);
                if (IsCollisionWithTerrain(obj))
                {
                    cameraPosition = obj.LastPosition;
                    obj.Position = obj.LastPosition;
                }
            }

            if (state.IsKeyDown(Keys.J))
            {
                obj.LastPosition = obj.Position;
                obj.Position -= new Vector3(obj.forwardSpeed, 0, 0);
                if (IsCollisionWithTerrain(obj))
                {
                    cameraPosition = obj.LastPosition;
                    obj.Position = obj.LastPosition;
                }
            }
            else if (state.IsKeyDown(Keys.L))
            {
                obj.LastPosition = obj.Position;
                obj.Position += new Vector3(obj.forwardSpeed, 0, 0);
                if (IsCollisionWithTerrain(obj))
                {
                    cameraPosition = obj.LastPosition;
                    obj.Position = obj.LastPosition;
                }
            }

            if (state.IsKeyDown(Keys.U))
            {
                obj.LastPosition = obj.Position;
                obj.Position += new Vector3(0, 0, obj.forwardSpeed);
                if (IsCollisionWithTerrain(obj))
                {
                    cameraPosition = obj.LastPosition;
                    obj.Position = obj.LastPosition;
                }
            }
            else if (state.IsKeyDown(Keys.O))
            {
                obj.LastPosition = obj.Position;
                obj.Position -= new Vector3(0, 0, obj.forwardSpeed);
                if (IsCollisionWithTerrain(obj))
                {
                    cameraPosition = obj.LastPosition;
                    obj.Position = obj.LastPosition;
                }
            }

            obj.World = Matrix.CreateTranslation(obj.Position);
        }

        #endregion

        #region Update and Draw Functions

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if ((GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) || 
                (Keyboard.GetState().IsKeyDown(Keys.Escape)))
                this.Exit();

            MoveObjectonWithRotation(chest);
            MoveObjectonByPosition(ship);

            CollisionCheck();
            VictoryCheck();

            base.Update(gameTime);
        }

        void UpdatePosition()
        {
            KeyboardState keyboardState = Keyboard.GetState();
            GamePadState currentState = GamePad.GetState(PlayerIndex.One);

            if (keyboardState.IsKeyDown(Keys.Left) || (currentState.DPad.Left == ButtonState.Pressed))
            {
                // Rotate left.
                player1.objectYaw += player1.rotationSpeed;
            }
            if (keyboardState.IsKeyDown(Keys.Right) || (currentState.DPad.Right == ButtonState.Pressed))
            {
                // Rotate right.
                player1.objectYaw -= player1.rotationSpeed;
            }
            if (keyboardState.IsKeyDown(Keys.Up) || (currentState.DPad.Up == ButtonState.Pressed))
            {
                Matrix forwardMovement = Matrix.CreateRotationY(player1.objectYaw);
                Vector3 v = new Vector3(0, 0, player1.forwardSpeed);
                v = Vector3.Transform(v, forwardMovement);
                player1.Position.Z += v.Z;
                player1.Position.X += v.X;
            }
            if (keyboardState.IsKeyDown(Keys.Down) || (currentState.DPad.Down == ButtonState.Pressed))
            {
                Matrix forwardMovement = Matrix.CreateRotationY(player1.objectYaw);
                Vector3 v = new Vector3(0, 0, -player1.forwardSpeed);
                v = Vector3.Transform(v, forwardMovement);
                player1.Position.Z += v.Z;
                player1.Position.X += v.X;
            }

            if (keyboardState.IsKeyDown(Keys.PageUp))
            {
                player1.Position.Y += player1.forwardSpeed;
            }
            else if (keyboardState.IsKeyDown(Keys.PageDown))
            {
                player1.Position.Y -= player1.forwardSpeed;
            }

            player1.bSphere.Center = player1.Position;
        }

        // Should only be called for player
        void UpdateCamera()
        {
            Matrix rotationMatrix = Matrix.CreateRotationY(player1.objectYaw);

            // Create a vector pointing the direction the camera is facing.
            Vector3 transformedReference = Vector3.Transform(cameraReference, rotationMatrix);

            // Calculate the position the camera is looking at.
            player1.cameraLookAt = player1.Position + transformedReference;

            // Set up the view matrix and projection matrix.
            player1.View = Matrix.CreateLookAt(player1.Position, player1.cameraLookAt, new Vector3(0.0f, 1.0f, 0.0f));

            Viewport viewport = graphics.GraphicsDevice.Viewport;
            float aspectRatio = (float)viewport.Width / (float)viewport.Height;

            player1.Proj= Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio, nearClip, farClip);
            debug.CameraPosition = player1.GetPositionToString();

            //Flash Light
            //flashLight.Enabled = true;
            //flashLight.Direction = cameraLookat;
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.White);

            UpdatePosition();
            UpdateCamera();
            DrawObject(terrain);
            DrawModel(terrain);
            DrawObject(ducky);
            DrawModel(ducky);
            DrawObject(chest);
            DrawModel(chest);
            DrawObject(ship);
            DrawModel(ship);

            spriteBatch.Begin();
            spriteBatch.DrawString(debug.font, DisplayGoals.SendAnnouncement(), DisplayGoals.Position, debug.Color);
            spriteBatch.DrawString(debug.font, DisplayControls.SendAnnouncement(), DisplayControls.Position, debug.Color);
            spriteBatch.DrawString(debug.font, debug.CameraPosition, debug.FontPosition, Color.Black);
            if (DisplayCollision.print)
                spriteBatch.DrawString(debug.font, DisplayCollision.SendAnnouncement(), 
                    DisplayCollision.Position, DisplayCollision.Color);
            
            spriteBatch.End();

            base.Draw(gameTime);
        }

        void DrawObject(GameObject obj)
        {
            foreach (ModelMesh mesh in obj.model.Meshes)
            {
                foreach (BasicEffect be in mesh.Effects)
                {
                    be.Projection = player1.Proj;
                    be.View = player1.View;
                    be.World = obj.World;
                    if (obj.texture != null)
                    {
                        be.Texture = obj.texture;
                        be.TextureEnabled = true;
                    }
                }
                mesh.Draw();
            }
        }

        private void DrawModel(GameObject m)
        {
            Matrix[] transforms = new Matrix[m.model.Bones.Count];
            float aspectRatio = graphics.GraphicsDevice.Viewport.Width / graphics.GraphicsDevice.Viewport.Height;
            m.model.CopyAbsoluteBoneTransformsTo(transforms);
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                aspectRatio, 1.0f, 10000.0f);
            Matrix view = Matrix.CreateLookAt(new Vector3(0.0f, 50.0f, m.Zoom), Vector3.Zero, Vector3.Up);

            foreach (ModelMesh mesh in m.model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();

                    effect.View = view;
                    effect.Projection = projection;
                    effect.World = Matrix.CreateWorld(cameraReference, m.rotation, new Vector3(0,1,0));
                }
                mesh.Draw();
            }
        }
        #endregion
    }
}
